/*
*  notification.c - Notification server functions
*  Copyright (c) 2005 Derek Ingrouville (dergro@users.sourceforge.net)
*
*  This program is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Library General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

#include <stdlib.h>
#include <string.h>

#include "notification.h"
#include "challenges.h"
#include "twnauth.h"
#include "crypto.h"
#include "utility.h"

int send_ver (MsnConnection *conn);
int send_cvr (MsnConnection *conn, char *passport);
int send_usr (MsnConnection *conn, char *passport);
int msn_notification_send_ticket (MsnNotification *notif, char *ticket);
int send_syn (MsnConnection *conn);
int send_out (MsnConnection *conn);

static void msn_notification_disconnect_cb (MsnConnection *conn);
static void msn_notification_setup_callbacks (MsnNotification *notif);

MsnNotification *
msn_notification_new (MsnSession *session)
{
	MsnNotification *notif;

	g_return_val_if_fail (session != NULL, NULL);

	notif = g_new (MsnNotification, 1);
	notif->conn = msn_connection_new (session);
	notif->session = session;
	notif->connected = FALSE;

	msn_notification_setup_callbacks (notif);

	return notif;
}

void
msn_notification_free (MsnNotification *notif)
{
	g_return_if_fail (notif != NULL);

	if (notif->connected)
		msn_notification_disconnect (notif);

	msn_connection_free (notif->conn);

	g_free (notif);

	return;
} 

int
msn_notification_connect (MsnNotification *notif, const char *server, int port)
{
	int ret;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (server != NULL, -1);
	g_return_val_if_fail (port > 0, -1);

	if (notif->connected)
		msn_notification_disconnect (notif);

	ret = msn_connection_connect (notif->conn, server, port);
	if (ret != 0)
		return -1;

	/*msn_connection_set_disconnect_cb (notif->conn,
	msn_notification_disconnect_cb);*/


	notif->connected = TRUE;
	return ret;
}

void
msn_notification_disconnect (MsnNotification *notif)
{
	g_return_if_fail (notif != NULL);

	if (!notif->connected)
		return;

	/* Don't call disconnect callback, we know we are disconnecting */
	/*msn_connection_set_disconnect_cb (notif->conn, NULL);*/

	msn_connection_disconnect (notif->conn);
	notif->connected = FALSE;
}

void
msn_notification_disconnect_cb (MsnConnection *conn)
{
	/* do we really need this?*/
	/* not really possible without conn->notif.. */
	/*conn->session->notif->connected = FALSE;*/
	return;
}

int
msn_notification_start_login (MsnNotification *notif)
{
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);

	ret = send_ver (notif->conn);

	return ret;
}

void
msn_notification_sign_out (MsnNotification *notif)
{
	g_return_if_fail (notif != NULL);

	if (!notif->connected)
		return;

	send_out (notif->conn);
	msn_notification_disconnect (notif);

	return;
}

/* ***** Login functions ***** */
int
send_ver (MsnConnection *conn)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->connected, -1);

	ret = msn_connection_send_cmd (conn, "VER %i MSNP11 CVR0\r\n", conn->trid);

	return ret;
}

static int
ver_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* VER ID PROTO PROTO CVR0 */

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	if (strcmp (cmd->name, "VER") != 0)
		return -1;

	send_cvr (notif->conn, notif->session->acct->passport);

	return 0;
}

int
send_cvr (MsnConnection *conn, char *passport)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->connected, -1);
	g_return_val_if_fail (passport != NULL, -1);

	ret = msn_connection_send_cmd (conn, "CVR %d %s %s\r\n",
		conn->trid,
		"0x040c winnt 5.1 i386 MSNMSGR 7.0.0777 msmsgs",
		passport);

	return ret;
}

static int
cvr_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	if (strcmp (cmd->name, "CVR") != 0)
		return -1;

	send_usr (notif->conn, notif->session->acct->passport);

	return 0;
}

int
send_usr (MsnConnection *conn, char *passport)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->connected, -1);
	g_return_val_if_fail (passport != NULL, -1);

	ret = msn_connection_send_cmd (conn, "USR %d TWN I %s\r\n", conn->trid, passport);

	return ret;
}

static int
xfr_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	int port = 0;
	char **ip_port = NULL;
	int ret = -1;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	if (strcmp (cmd->name, "XFR") != 0)
		return -1;

	if (strcmp (cmd->params[1], "NS") == 0) {
		/* XFR TRID NS NOTIF_IP:PORT 0 DISP_IP:PORT */
		ip_port = g_strsplit (cmd->params [2], ":", 2);
		if (ip_port == NULL)
			return -1;

		port = atoi (ip_port[1]);

		/* We are currently connected to dispatch server, 
		* so we disconnect, then connect to notification */
		ret = msn_notification_connect (notif, ip_port[0], port);
		g_strfreev (ip_port);
		if (ret < 0) {
			return -1;
		}

		/* Start handshake with notif server */
		ret = msn_notification_start_login (notif);
	}
	else if (strcmp (cmd->params[1], "SB") == 0) {
		/* XFR TRID SB IP:PORT CKI AUTHSTR */
		MsnSwitchboard *sboard = NULL;
		unsigned int trid = 0;

		trid = atoi (cmd->params [0]);
		sboard = msn_session_find_switchboard_from_id (notif->session, trid);
		if (sboard == NULL)
			return -1;

		ip_port = g_strsplit (cmd->params [2], ":", 2);
		port = atoi (ip_port[1]);

		ret = msn_switchboard_create_session (sboard, ip_port [0], port, cmd->params [4]);
	}

	return ret;
}

int
msn_notification_send_ticket (MsnNotification *notif, char *ticket)
{
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (ticket, -1);

	ret = msn_connection_send_cmd (notif->conn,
		"USR %d TWN S %s\r\n",
		notif->conn->trid,
		ticket);

	return ret;
}

static int
usr_cmd (MsnNotification *notif, MsnCommand *cmd)
{ 
	int ret;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	if (strcmp (cmd->name, "USR") != 0)
		return -1;

	if (strcmp (cmd->params[1], "TWN") == 0) {
		/* Auth stage */
		/* USR ID TWN S AUTHSTR */

		char *login_server = NULL;
		char *authstr = NULL;   /* Needed for authentication */
		char *ticket = NULL;    /* Needed to complete sign in */

		authstr = cmd->params [cmd->param_count - 1];
		login_server = get_login_server_from_nexus ();
		ticket = msn_get_twn_ticket (notif->session, login_server, authstr);
		g_free (login_server);

		if (ticket == NULL)
			return -1;

		ret = msn_notification_send_ticket (notif, ticket);
		g_free (ticket);
		if (ret < 0)
			return -1;
	}
	else if (strcmp (cmd->params[1], "OK") == 0) {
		/* Auth complete */
		/* USR ID OK passport BOOL_VERIFIED 0 */

		/* Sync contact list */
		ret = send_syn (notif->conn);
		if (ret < 0)
			return -1;
	}

	return 0;
}

/* ***** Contact list Syncronization functions ***** */
int
send_syn (MsnConnection *conn)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);

	/* TODO: timestamp and clist caching */
	ret = msn_connection_send_cmd (conn, "SYN %d 0 0\r\n", conn->trid);

	return ret;
}

static int
syn_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* SYN ID TIMESTAMP1 TIMESTAMP2 #CONTACTS #GROUPS */
	int clist_ver = 0;

	/* TODO: compare timestamps properly handle caching */
	
	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	notif->session->clist = msn_contactlist_from_syn (cmd);
	if (notif->session->clist == NULL)
		return -1;

	return 0;
}

static int
lsg_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* LSG grp_name grp_guid */
	MsnGroup *group = NULL;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	group = msn_group_from_lsg (cmd);
	if (group == NULL)
		return -1;

	if (msn_clist_add_group (notif->session->clist, group)) {
		g_free (group);
		return -1;
	}

	msn_event_add (notif->conn->events, "group_added", group);

	return 0;
}

int
send_adg (MsnConnection *conn, const char *group_name)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);
	g_return_val_if_fail (group_name != NULL, -1);

	ret = msn_connection_send_cmd (conn, "ADG %d %s 0\r\n",
		conn->trid, group_name);

	return ret;
}

static int
adg_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* ADG trid clist_ver group_name grp_id 0 */
	MsnGroup *group = NULL;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	group = msn_group_from_adg (cmd);
	if (group == NULL)
		return -1;

	if (msn_clist_add_group (notif->session->clist, group)) {
		g_free (group);
		return -1;
	}

	msn_event_add (notif->conn->events, "group_added", group);

	return 0;
}

int
send_rmg (MsnConnection *conn, int group_id)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);
	g_return_val_if_fail (group_id > 0, -1);

	ret = msn_connection_send_cmd (conn, "RMG %d %d\r\n",
		conn->trid, group_id);

	return ret;
}

int
send_sbp (MsnConnection *conn, const char *contact_guid, const char *nickname)
{
	int ret = 0;
	char *url_nick = NULL;

	/* SBP trid contact_guid MFN url_encoded_nickname */
	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);
	g_return_val_if_fail (contact_guid != NULL, -1);
	g_return_val_if_fail (nickname != NULL, -1);

	url_nick = url_encode (nickname);
	if (url_nick == NULL)
		return -1;
	
	ret = msn_connection_send_cmd (conn, "SBP %d %s %s %s\r\n",
								   conn->trid, 
								   contact_guid,
								   url_nick);

	g_free (url_nick);
	
	return ret;
}

static int
/* TODO: fix this */
rmg_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* RMG trid clist_ver grp_id */
	/*MsnGroup *group = NULL;
	int *grp_id = NULL;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	g_print (cmd->params [0]);
	g_print (cmd->params [1]);
	g_print (cmd->params [2]);

	group = msn_clist_find_group (notif->session->clist, atoi (cmd->params [2]));
	if (group == NULL)
		return -1;

	grp_id = g_new (int, 1);
	*grp_id = group->id;

	msn_clist_remove_group (notif->session->clist, group);

	msn_event_add (notif->conn->events, "group_removed", grp_id);

	return 0;*/
}

static int
lst_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* LST N=email F=nickname [C=contact_guid] lists [comma sep grps list] */
	
	/* Note: the contact_guid, F=nickname, and group list fields are optional
	 * C= and the grps will not appear if the contact is not on the forward list
	 * the F=nickname field may or may not appear if the contact is not on the FL
	 * a contact does not need to belong to a group so there may be no grp list */
	
	MsnContact *contact = NULL;
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	contact = msn_contact_from_lst (cmd);
	if (contact == NULL) {
		g_printf ("contact is null\n");
		return -1;
	}

	ret = msn_clist_add_contact (notif->session->clist, contact);
	if (ret < 0) {
		g_printf ("clist_add_contact failed\n");
		msn_contact_free (contact);
		return -1;
	}

	if (ret == 0)
		msn_event_add (notif->conn->events, "contact_added", contact);

	g_printf ("num_contacts = %d\n", notif->session->clist->num_contacts);
	g_printf ("syn_num_contacts = %d\n", notif->session->clist->syn_num_contacts);
	if (notif->session->clist->num_contacts == notif->session->clist->syn_num_contacts)
		msn_event_add (notif->conn->events, "sync_finished", notif->session);

	return 0;
}

static int
gtc_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	return 0;
}

static int
blp_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	return 0;
}

static int
prp_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);
	
	g_printf ("PRP: %s\n", cmd->command);

	if (strcmp (cmd->params[0], "MFN") == 0) {
		/* PRP MFN nickname */
		char *nick = NULL;

		/* TODO: nickname is UTF8. Make sure we handle it properly */
		nick = url_decode (cmd->params [1]);
		msn_account_set_nickname (notif->session->acct, nick);
		msn_event_add (notif->conn->events, "user_nickname_changed", nick);
	}

	return 0;
}

static int
bpr_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	return 0;
}

static int
sbs_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	return 0;
}

static int
sbp_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* TODO: implement contact renaming */
	return 0;
}

static int
ubx_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	int payload = cmd->params[1];
	char *buf = g_new0 (char, payload + 1);
	msn_connection_read (notif->conn, buf, payload);
	
	g_print ("cmd: %s\n", cmd->command);
	g_print ("payload: %s\n", buf);

	return 0;
}

/* ***** Status functions ***** */
int
send_xfr (MsnConnection *conn, char *where)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);
	g_return_val_if_fail (where != NULL, -1);

	ret = msn_connection_send_cmd (conn, "XFR %d %s\r\n", conn->trid, where);

	return ret;
}

int
send_chg (MsnConnection *conn, const char *status, unsigned int clientcaps)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->connected, -1);
	g_return_val_if_fail (status != NULL, -1);

	ret = msn_connection_send_cmd (conn, "CHG %d %s %d\r\n", 
		conn->trid, 
		status, 
		clientcaps);

	return ret;
}

int
send_prp (MsnConnection *conn, const char *nickname)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);
	g_return_val_if_fail (nickname != NULL, -1);

	ret = msn_connection_send_cmd (conn, "PRP %d %s\r\n", conn->trid, nickname);

	return ret;
}

static int
chg_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* CHG TRID STATUS CID */
	MsnAccount *acct = NULL;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);
	
	acct = notif->session->acct;
	g_assert (acct != NULL);
	g_assert (cmd->params [1] != NULL);

	if (strcmp (cmd->params[1], "NLN") == 0)
		acct->status = ONLINE;
	else if (strcmp (cmd->params[1], "AWY") == 0)
		acct->status = AWAY;
	else if (strcmp (cmd->params[1], "IDL") == 0)
		acct->status = IDLE;
	else if (strcmp (cmd->params[1], "BRB") == 0)
		acct->status = BRB;
	else if (strcmp (cmd->params[1], "BSY") == 0)
		acct->status = BUSY;
	else if (strcmp (cmd->params[1], "LUN") == 0)
		acct->status = LUNCH;
	else if (strcmp (cmd->params[1], "PHN") == 0)
		acct->status = PHONE;
	else if (strcmp (cmd->params[1], "HDN") == 0)
		acct->status = HIDDEN;

	msn_event_add (notif->conn->events, "user_status_changed", acct);

	return 0;
}

static int
nln_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* NLN STATUS PASSPORT NICK CID */
	MsnContact *contact = NULL;
	char *url_decoded = NULL;
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	contact = msn_clist_find_contact (notif->session->clist, cmd->params[1]);
	if (contact == NULL)
		return -1;

	if (strcmp (cmd->params[0], "NLN") == 0)
		contact->status = ONLINE;
	else if (strcmp (cmd->params[0], "AWY") == 0)
		contact->status = AWAY;
	else if (strcmp (cmd->params[0], "IDL") == 0)
		contact->status = IDLE;
	else if (strcmp (cmd->params[0], "BSY") == 0)
		contact->status = BUSY;
	else if (strcmp (cmd->params[0], "BRB") == 0)
		contact->status = BRB;
	else if (strcmp (cmd->params[0], "PHN") == 0)
		contact->status = PHONE;
	else if (strcmp (cmd->params[0], "LUN") == 0)
		contact->status = LUNCH;
	else if (strcmp (cmd->params[0], "FLN") == 0)
		contact->status = OFFLINE;

	url_decoded = url_decode (cmd->params[2]);
	msn_contact_set_nickname (contact, url_decoded);
	g_free (url_decoded);

	if (cmd->param_count == 5) {
		/* We have an MsnObject available */
		if (cmd->params [4] != NULL) {
			MsnObject *obj = NULL;
			char *tmp = NULL;
			char *str = NULL;

			str = url_decode (cmd->params [4]);
			obj = msn_object_new_from_string (str);
			msn_contact_set_msn_object (contact, obj);

			g_free (str);
		}
	}

	msn_event_add (notif->conn->events, "contact_info_changed", contact);

	return 0;
}

static int
fln_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* FLN PASSPORT */
	MsnContact *contact = NULL;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	contact = msn_clist_find_contact (notif->session->clist, cmd->params[0]);
	if (contact == NULL)
		return -1;

	contact->status = OFFLINE;

	msn_event_add (notif->conn->events, "contact_info_changed", contact);

	return 0;
}

static int
iln_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* ILN TRID STATUS PASSPORT SCREENNAME CID */
	MsnContact *contact = NULL;
	char *url_decoded = NULL;
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	contact = msn_clist_find_contact (notif->session->clist, cmd->params[2]);

	if (contact == NULL)
		return -1;

	if (strcmp (cmd->params[1], "NLN") == 0)
		contact->status = ONLINE;
	else if (strcmp (cmd->params[1], "AWY") == 0)
		contact->status = AWAY;
	else if (strcmp (cmd->params[1], "IDL") == 0)
		contact->status = IDLE;
	else if (strcmp (cmd->params[1], "BSY") == 0)
		contact->status = BUSY;
	else if (strcmp (cmd->params[1], "BRB") == 0)
		contact->status = BRB;
	else if (strcmp (cmd->params[1], "PHN") == 0)
		contact->status = PHONE;
	else if (strcmp (cmd->params[1], "LUN") == 0)
		contact->status = LUNCH;
	else if (strcmp (cmd->params[1], "FLN") == 0)
		contact->status = OFFLINE;

	url_decoded = url_decode (cmd->params[3]);
	msn_contact_set_nickname (contact, url_decoded);
	g_free (url_decoded);

	if (cmd->param_count == 6) {
		/* We have an MsnObject available */
		if (cmd->params [5] != NULL) {
			MsnObject *obj = NULL;
			char *tmp = NULL;
			char *str = NULL;

			str = url_decode (cmd->params [5]);
			obj = msn_object_new_from_string (str);
			msn_contact_set_msn_object (contact, obj);

			g_free (str);
		}
	}

	msn_event_add (notif->conn->events, "contact_info_changed", contact);

	return 0;
}

static int
rea_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* REA TRID CLIST_VERSION PASSPORT NICKNAME */
	MsnContact *contact = NULL;
	char *passport = NULL;
	char *nickname = NULL;
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);
	g_return_val_if_fail (cmd->params[2] != NULL, -1);
	g_return_val_if_fail (cmd->params[3] != NULL, -1);

	passport = url_decode (cmd->params[2]);
	nickname = url_decode (cmd->params[3]);

	/* For now we will only use REA to change your display name */
	if (strcmp (passport, notif->session->acct->passport) != 0) {
		g_free (passport);
		g_free (nickname);
		return 0;
	}

	msn_account_set_nickname (notif->session->acct, nickname);
	msn_event_add (notif->conn->events, "user_nickname_changed", nickname);

	g_free (passport);

	return 0;
}

int
send_out (MsnConnection *conn)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);

	ret = msn_connection_send_cmd (conn, "OUT\r\n");

	return ret;
}

int
send_qry (MsnConnection *conn, char *challstr)
{
	char *hash = NULL;
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);
	g_return_val_if_fail (challstr != NULL, -1);

	hash = msn_process_challenge (challstr);
	if (hash == NULL)
		return -1;
	
	ret = msn_connection_send_cmd (conn,
		"QRY %d %s 32\r\n%s",
		conn->trid,
		MSN_PRODUCT_ID,
		hash);

	g_free (hash);

	return ret;
}

static int
chl_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* CHL TRID CHALLSTR */
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	ret = send_qry (notif->conn, cmd->params[1]);

	return ret;
}

int
send_png (MsnConnection *conn)
{
	int ret = 0;

	g_return_val_if_fail (conn != NULL, -1);
	g_return_val_if_fail (conn->socket->connected, -1);

	ret = msn_connection_send_cmd (conn, "PNG\r\n");

	return ret;
}

static int
rng_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	if (strcmp (cmd->name, "RNG") != 0)
		return -1;

	/* Command will not be freed when this returns */
	msn_command_ref (cmd);

	msn_event_add (notif->conn->events, "switchboard_invited", cmd);

	return 0;
}

static int
out_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->params [0] != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);

	if (strcmp (cmd->params [0], "OTH") == 0)
		msn_event_add (notif->conn->events, "signed_on_elsewhere", NULL);
	else if (strcmp (cmd->params [0], "SSD") == 0)
		msn_event_add (notif->conn->events, "server_maintainence", NULL);

	return 0;
}

MsnSwitchboard *
msn_notification_request_switchboard (MsnNotification *notif)
{
	MsnSwitchboard *sboard = NULL;
	unsigned int trid = 0;
	int ret = 0;

	g_return_val_if_fail (notif != NULL, NULL);
	g_return_val_if_fail (notif->connected, NULL);

	trid = notif->conn->trid;

	ret = msn_connection_send_cmd (notif->conn, "XFR %i SB\r\n", trid);
	if (ret < 0)
		return NULL;

	sboard = msn_switchboard_new (notif->session, trid, SWITCHBOARD_CHAT_NEW);

	return sboard;
}

int
msg_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	MsnCallback func = NULL;
	MsnMessage *msg = NULL;
	int ret = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	g_return_val_if_fail (cmd->param_count > 0, -1);
	g_return_val_if_fail (cmd->params[0] != NULL, -1);

	msg = msn_connection_recv_msg (notif->conn, cmd->command);
	if (msg == NULL)
		return -1;

	func = msn_callbacks_lookup (notif->conn->callbacks, msg->content_type);
	if (func == NULL) {
		/* Unhandled Command */
		g_message ("Unhandled message type: %s\r\n", msg->content_type);
		return 0;
	}

	ret = func (notif->conn->callbacks->cmd_data, msg);

	/* TODO: make sure func isn't keeping message after it shouldn't */
	/* Should we free here, or in each func? here seems appropriate */
	msn_message_free (msg);

	return ret;
}

int
profile_msg (MsnNotification *notif, MsnMessage *msg)
{
	int ret = msn_account_set_profile (notif->session->acct, msg);
	return ret;
}

int
email_msg (MsnNotification *notif, MsnMessage *msg)
{
	return 0;
}

static int
not_cmd (MsnNotification *notif, MsnCommand *cmd)
{
	/* NOT LEN */
	char *buf = NULL;
	guint len = 0;
	int ret = 0;
	guint total = 0;

	g_return_val_if_fail (notif != NULL, -1);
	g_return_val_if_fail (notif->connected, -1);
	g_return_val_if_fail (cmd != NULL, -1);
	
	len = atoi (cmd->params [0]);

	buf = g_new (char, len + 1);

	while (total < len) {
		/* Read in data, we are not going to process it */
		/* Data will overwrite previously received data */
		ret = msn_connection_read (notif->conn, buf, len - total);
		if (ret <= 0)
			break;
		total += ret;
	}

	buf [len] = '\0';

	g_print ("Notification: \n{%s}\n", buf);

	g_free (buf);

	return 0;
}

void
msn_notification_setup_callbacks (MsnNotification *notif)
{
	MsnCallbacks *cbs = NULL;

	g_return_if_fail (notif != NULL);

	cbs = notif->conn->callbacks;
	g_return_if_fail (cbs != NULL);

	msn_callbacks_set_cmd_data (cbs, notif);

	/* Login */
	msn_callbacks_add_cmd (cbs, "VER", ver_cmd);
	msn_callbacks_add_cmd (cbs, "CVR", cvr_cmd);
	msn_callbacks_add_cmd (cbs, "XFR", xfr_cmd);
	msn_callbacks_add_cmd (cbs, "USR", usr_cmd);

	/* Clist */
	msn_callbacks_add_cmd (cbs, "SYN", syn_cmd);
	msn_callbacks_add_cmd (cbs, "LSG", lsg_cmd);
	msn_callbacks_add_cmd (cbs, "ADG", adg_cmd);
	msn_callbacks_add_cmd (cbs, "RMG", rmg_cmd);
	msn_callbacks_add_cmd (cbs, "LST", lst_cmd);
	msn_callbacks_add_cmd (cbs, "GTC", gtc_cmd);
	msn_callbacks_add_cmd (cbs, "BLP", blp_cmd);
	msn_callbacks_add_cmd (cbs, "PRP", prp_cmd);
	msn_callbacks_add_cmd (cbs, "BPR", bpr_cmd);
	msn_callbacks_add_cmd (cbs, "SBS", sbs_cmd);
	msn_callbacks_add_cmd (cbs, "SBP", sbp_cmd);
	msn_callbacks_add_cmd (cbs, "UBX", ubx_cmd);
	
	/* Status notifications */
	msn_callbacks_add_cmd (cbs, "CHG", chg_cmd);
	msn_callbacks_add_cmd (cbs, "NLN", nln_cmd);
	msn_callbacks_add_cmd (cbs, "FLN", fln_cmd);
	msn_callbacks_add_cmd (cbs, "ILN", iln_cmd);

	/* Session */
	msn_callbacks_add_cmd (cbs, "RNG", rng_cmd);
	msn_callbacks_add_cmd (cbs, "CHL", chl_cmd);
	msn_callbacks_add_cmd (cbs, "OUT", out_cmd);

	/* Message Handlers */
	msn_callbacks_add_cmd (cbs, "MSG", msg_cmd);
	msn_callbacks_add_msg (cbs, "text/x-msmsgsprofile", profile_msg);
	msn_callbacks_add_msg (cbs, 
		"text/x-msmsgsinitialemailnotification",
		email_msg);
	msn_callbacks_add_cmd (cbs, "NOT", not_cmd);
}
